/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.view;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.jcr.RepositoryException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import offset.nodes.Constants;

/**
 *
 * @author Walter Lütgenau
 */
public class FacesUtils {

    public static final String SEPARATOR = "/";

    public static String getContextPath(String url) {
        try {
            URL u = new URL(url);
            String path = u.getPath().substring(1);
            String context = null;
            if (path.indexOf(SEPARATOR) > 0)
                context = path.substring(0, path.indexOf(SEPARATOR));
            else
                context = path;
            return url.substring(0, url.indexOf(context) + context.length());
        } catch (MalformedURLException ex) {
            Logger.getLogger(FacesUtils.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    public static String getContextPath(HttpServletRequest request) {
        return getContextPath(request.getRequestURL().toString());
    }

    public static String getServletPath(String url, String servletMapping) {
        return getContextPath(url) + "/" + servletMapping;
    }

    public static String getContextPath() {
        return getContextPath((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest());
    }

    public static String getDialogPath() {
        return getContextPath() + "/" + Constants.SERVLETMAPPING_DIALOG;
    }

    public static String getRepositoryPath() {
        return getContextPath() + "/" + Constants.SERVLETMAPPING_REPOSITORY;
    }

    public static String getRepositoryPath(HttpServletRequest request) {
        return getContextPath(request) + "/" + Constants.SERVLETMAPPING_REPOSITORY;
    }

    public static HttpServletRequest getRequest() {
        return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
    }

    public static HttpSession getSession() {
        return getRequest().getSession();
    }
    
    public static Locale getLocale() {
        return FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
    }

    /**
     * Return a request or session scoped attribute object. Try request scope first.
     *
     * @param name
     * @return
     */
    public static Object getAttribute(String name) {
        Object result = FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get(name);
        if (result != null)
            return result;

        return FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(name);
    }

    /**
     * Return a backing bean, regardless if it is in session or request scope
     * 
     * @param beanClass
     * @return
     */
    public static Object getBean(Class beanClass) {
        return getAttribute(beanClass.getSimpleName());
    }

    public static ValueExpression createValueExpression(String valueExpression, Class<?> valueType) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        return facesContext.getApplication().getExpressionFactory().createValueExpression(
                facesContext.getELContext(), valueExpression, valueType);
    }

    public static MethodExpression createMethodExpression(String actionExpression, Class<?> returnType, Class[] parameters) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        return facesContext.getApplication().getExpressionFactory().createMethodExpression(
                facesContext.getELContext(), actionExpression, returnType, parameters);
    }
    
    public static Object resolveExpression(ValueExpression expression) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        return expression.getValue(facesContext.getELContext());
    }

    public static String getPath() throws RepositoryException {
        return ((PathBean) getBean(PathBean.class)).getPath();
    }

    public static String getContentType() throws RepositoryException {
        return ((PathBean) getBean(PathBean.class)).getContentType();
    }

    public static final String IN_REFRESH = "inRefresh";
    /**
     * Forward to the DispatchAction again, to create all necessary objects again.
     *
     * @throws RepositoryException
     * @throws IOException
     */
    public static void refresh() throws RepositoryException, IOException {
        if (getAttribute(IN_REFRESH) != null)
            return;

        String path = getPath();
        FacesContext oldContext = FacesContext.getCurrentInstance();

        // Set new context
        InnerFacesContext.setFacesContextAsCurrentInstance(newFacesContext(
                (HttpServletRequest) oldContext.getExternalContext().getRequest(),
                (HttpServletResponse) oldContext.getExternalContext().getResponse()));

        getRequest().setAttribute(IN_REFRESH, Boolean.TRUE.toString());
        
        // forward to the page via the dispatcher.
        FacesContext.getCurrentInstance().getExternalContext().dispatch("../../registry" + path);

        oldContext.responseComplete(); // there has been a response already by the forward
        // Set old context to avoid exceptions
        InnerFacesContext.setFacesContextAsCurrentInstance(oldContext);
    }

    // You need an inner class to be able to call FacesContext.setCurrentInstance
    // since it's a protected method
    private abstract static class InnerFacesContext extends FacesContext {

        protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) {
            FacesContext.setCurrentInstance(facesContext);
        }
    }

    /**
     * Creates a new FacesContext for the forward. This way, the current context will not be modified.
     *
     * @param request
     * @param response
     * @return
     */
    private static FacesContext newFacesContext(HttpServletRequest request, HttpServletResponse response) {
        // Try to get it first
        FacesContext oldContext = FacesContext.getCurrentInstance();
        FacesContext context;

        FacesContextFactory contextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
        LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
        Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);

        // Either set a private member servletContext = filterConfig.getServletContext();
        // in you filter init() method or set it here like this:
        // ServletContext servletContext = ((HttpServletRequest)request).getSession().getServletContext();
        // Note that the above line would fail if you are using any other protocol than http

        // Doesn't set this instance as the current instance of FacesContext.getCurrentInstance
        context = contextFactory.getFacesContext(oldContext.getExternalContext().getContext(), request, response, lifecycle);

        // set a new viewRoot, otherwise context.getViewRoot returns null
        UIViewRoot view = context.getApplication().getViewHandler().createView(context, oldContext.getViewRoot().getViewId());
        context.setViewRoot(view);

        return context;
    }

}
